package com.lagou.edu.support;

import com.lagou.edu.annotation.Autowired;
import com.lagou.edu.annotation.Transactional;
import com.lagou.edu.factory.ProxyFactory;

import java.io.File;
import java.io.FileFilter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author xgf
 * 自定义ioc容器读取注解
 */
public class CustomAnnotationConfigApplicationContext {
    //包名
    private String packageName;
    //定义一个并发容器
    private ConcurrentHashMap<String, Object> beans = new ConcurrentHashMap<>();

    /*
    解析包名，初始化方法
     */
    public CustomAnnotationConfigApplicationContext(String packageName) {
        this.packageName = packageName;
        initBeans();
    }

    private void initBeans() {
        //1.使用java反射机制扫包，获取当前包下所有类
        List<Class> classList = getClasses(packageName);
        //找出所有类并利用反射进行实例化对象放入beans中
        findClassIsAddedCostomAnnotation(classList);
    }

    /*
    根据包名扫描所有的class的集合
     */
    private List<Class> getClasses(String packageName) {
        // 第一个class类的集合
        List<Class> classList = new ArrayList<>();
        // 获取包的名字 并进行替换
        String packageDirName = packageName.replace(".", "/");
        // 定义一个枚举的集合 并进行循环来处理这个目录下的things
        Enumeration<URL> dirs;
        try {
            //获取包的路径集合
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            // 循环迭代下去
            while (dirs.hasMoreElements()) {
                // 获取下一个元素
                URL url = dirs.nextElement();
                // 得到协议的名称
                String protocol = url.getProtocol();
                // 如果是以文件的形式保存在服务器上
                if (protocol.equals("file")) {
                    // 获取包的物理路径
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    // 以文件的方式扫描整个包下的文件 并添加到集合中
                    findAndAddClassesInPackageByFile(packageName, filePath, classList);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return classList;
    }

    /*
         以文件的方式扫描整个包下的文件 并添加到集合中
     */
    private void findAndAddClassesInPackageByFile(String packageName, String filePath, List<Class> classList) {
        File dir = new File(filePath);
        //选出文件夹下面所有的文件
        File[] files = dir.listFiles(new FileFilter() {
            // 自定义过滤规则 如果可以循环(包含子目录) 或是以.class结尾的文件(编译好的java类文件)
            public boolean accept(File file) {
                return (file.isDirectory() || file.getName().endsWith(".class"));
            }
        });
        // 循环所有文件
        for (File file : files) {
            // 如果是目录 则继续扫描
            if (file.isDirectory()) {
                findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), classList);
            } else {
                // 如果是java类文件 去掉后面的.class 只留下类名
                String className = file.getName().substring(0, file.getName().length() - 6);
                try {
                    // 添加到集合中去
                    classList.add(Class.forName(packageName + "." + className));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /*
    将添加的class与注解进行装配
     */
    private void findClassIsAddedCostomAnnotation(List<Class> classList) {
        try{
            for (Class aClass : classList) {
                //循环类的集合与bean装配
                classToObjectIntoBeans(aClass, classList);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /*
        将符合的类与唯一的bean对象进行装配
     */
    private void classToObjectIntoBeans(Class aClass, List<Class> classList) {
        Object obj = null;//定义一个空对象用来存放反射实例化的对象

        try{
            //判断aclass是否为接口
            if(aClass.isInterface()){
                //遍历所有的类，找出不是接口的类的无参构造并通过反射技术实例化对象
                for(Class implClass : classList) {
                    //过滤掉所有接口
                    if (implClass.isInterface()) {
                        continue;
                    }
                    //对不是接口的类，通过类加载器获取类的全限定类名
                    Class fieldClassCopy = implClass.getClassLoader().loadClass(aClass.getName());
                    //判断implClass是否为类本身或者其子类
                    if (fieldClassCopy.isAssignableFrom(implClass)) {
                        //获取该类的所有公有构造方法
                        Constructor[] constructors = implClass.getConstructors();
                        for(Constructor constructor : constructors){
                            //获取构造函数的参数个数
                            int parameterCount = constructor.getParameterCount();
                            //如果是无参构造
                            if(parameterCount==0){
                                //通过反射技术实例化对象
                                obj = constructor.newInstance();
                            }
                        }
                        break;
                    }
                }
            } else {    //若aclass不是接口
                //获取该类所有的公有构造方法
                Constructor[] constructors = aClass.getConstructors();
                for(Constructor constructor : constructors){
                    int parameterCount = constructor.getParameterCount();
                    if(parameterCount==0){
                        //创建类的对象
                        obj = constructor.newInstance();
                    }
                }
            }
            //将实例化对象与aclass的类名一起装配好放入beans
            if (obj != null) {
                //beans(类名小写,classInfo)
                //获取当前类名
                String className = aClass.getSimpleName();
                //将类名首字母变为小写
                String beanID =toLowerCaseFirstOne(className);
                System.out.println("beanID = " + beanID);
                //装配类名和类的对象
                beans.put(beanID, obj);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public Object getBean(String beanName, CustomAnnotationConfigApplicationContext customAnnotationConfigApplicationContext) {
        //从spring 容器初始化对象
        Object beanObject = beans.get(beanName);
        try{
            //依赖注入容器对象
            referenceBindObject(beanObject);
        }catch (Exception e){
            e.printStackTrace();
        }
        //获取初始化的容器对象
        Class aClass = beanObject.getClass();
        //获取容器对象的注解
        Annotation annotation = aClass.getAnnotation(Transactional.class);
        //判断是否有注解
        if (annotation != null) {
            //对注解做动态代理
            ProxyFactory proxyFactory = (ProxyFactory) customAnnotationConfigApplicationContext.getBean("proxyFactory", customAnnotationConfigApplicationContext);
            beanObject = proxyFactory.getJdkProxy(beanObject);
        }
        return beanObject;
    }

    /*
        Autowired注解原理(作用域为Field）
     */
    private Object referenceBindObject(Object beanObject) {
        // 使用反射机制,获取当前类的对象
        Class beanClass = beanObject.getClass();
        //获取当前对象的所有属性
        Field[] declaredFields = beanClass.getDeclaredFields();
        try {
            // 判断当前类属性是否存在注解
            for (Field field : declaredFields) {
                if (!field.isAccessible()) {
                    // 是否允许访问私有属性
                    field.setAccessible(true);
                }
                //获取该属性中与属性参数类型匹配的公有注解对象
                Autowired filedAnnotation = field.getAnnotation(Autowired.class);
                //判断属性上面是否有加注解Autowired
                if (filedAnnotation == null) {
                    System.out.println(beanClass.getName() + "类中的" + field.getName() + "字段,该字段上没有加注解");
                    break;
                }
                //获取当前类对象
                Class fieldClass = field.getType();
                //获取类名
                String classSimpleName = fieldClass.getSimpleName();
                //获取容器中的当前类对象
                Object fieldObject = beans.get(toLowerCaseFirstOne(classSimpleName));
                //递归所有类对象
                Object object = referenceBindObject(fieldObject);
                //将依赖注入当前对象
                field.set(beanObject, object);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return beanObject;
    }
    // 首字母转小写
    public static String toLowerCaseFirstOne(String s) {
        if (Character.isLowerCase(s.charAt(0)))
            return s;
        else
            return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
    }

}
